﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UIElements;

public class Logger : MonoBehaviour
{
    public struct Entity
    {
        public int Level;
        public string Content;
    }

    public int MaxCount = 50;
    public Queue<Entity> messages = new Queue<Entity>();
    public Queue<Entity> messagesThread = new Queue<Entity>();
    public System.Action<Logger> onLogChanged;

    List<bool> _levelFilter = new List<bool>();
    List<int> _levelCount = new List<int>();

    public void SetLevelFilter(int i, bool enable) {  _levelFilter[i] = enable; _dirty = true; }
    public bool GetLevelFilter(int i) { return _levelFilter[i]; }
    public int GetLevelCount(int i) { return _levelCount[i]; }

    object _mutex = new object();

    void Start()
    {
        for (int i = 0; i < 20; i++) 
        {
            _levelFilter.Add(true);
            _levelCount.Add(0);
        }
        Application.logMessageReceivedThreaded += Application_logMessageReceived;
    }

    void OnDestroy()
    {
        Application.logMessageReceivedThreaded -= Application_logMessageReceived;
    }

    bool _dirty = false;
    void Update()
    {
        if (_dirty)
        {
            _dirty = false;
            lock (_mutex)
            {
                while (messagesThread.Count>0)
                {
                    var item = messagesThread.Dequeue();
                    messages.Enqueue(item);
                    _levelCount[item.Level]++;
                    if (messages.Count > MaxCount)
                    {
                        var r = messages.Dequeue();
                        _levelCount[r.Level]--;
                    }
                }
            }
            

            onLogChanged?.Invoke(this);
        }
    }

    private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
    {
        var now = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss");
        int level = 0;
        switch (type)
        {
            case LogType.Error:
                level = 2;
                //messages.Enqueue($"<color=red>[{now}]{condition}</color>");
                break;
            case LogType.Assert:
                level = 2;
                //messages.Enqueue($"<color=red>[{now}]{condition}</color>");
                break;
            case LogType.Warning:
                level = 1;
                //messages.Enqueue($"<color=yellow>[{now}]{condition}</color>");
                break;
            case LogType.Log:
                level = 0;
                //messages.Enqueue($"[{now}]{condition}");
                break;
            case LogType.Exception:
                level = 2;
                //messages.Enqueue($"<color=red>[{now}]{condition}</color>");
                break;
            default:
                break;
        }
        lock (_mutex)
        {
            messagesThread.Enqueue(new Entity { Level = level, Content = $"[{now}]{condition}" });
        }
        
        _dirty = true;
    }

    public int GetCount()
    {
        int n = 0;
        int i = 0;
        foreach (var c in _levelCount)
        {
            if (!_levelFilter[i])
                continue;

            n += c;
            i++;
        }
        return n;
    }

    public int GetCount(int level)
    {
        return _levelCount[level];
    }

    public Entity GetLine(int idx)
    {
        int i = 0;
        foreach (var message in messages)
        {
            if (!_levelFilter[message.Level])
                continue;

            if (i == idx)
            {
                return message;
            }

            i++;
        }
        return default;
    }

    public void ForeachLine(Action<int, Entity> callback)
    {
        int i = 0;
        foreach (var message in messages)
        {
            if (!_levelFilter[message.Level])
                continue;

            callback?.Invoke(i,message);
            i++;
        }
    }

    public void Clear()
    {
        messages.Clear();
        for (int i = 0; i < _levelCount.Count; i++)
        {
            _levelCount[i] = 0;
        }

        _dirty = true;
    }
}
